/*
 * Copyright (c) 2009 by unreserved.org.
 * All rights reserved.
 */

package org.unreserved.ladderplot;

import java.util.*;
import org.apache.log4j.Logger;

/**
 * Formats a label as a series of text lines. This is done according
 * to certain specifications, primarily a maximum line width.
 * 
 * <p>The input string can contain control sequences that influence
 * formatting: "\n" for newline, "\-" for an optional line break and
 * "\\" for the '\' character.
 */
public class LabelTextRenderer implements TextRenderer {
    private static final Logger logger = Logger.getLogger(LabelTextRenderer.class);
    
    protected Label label;
    protected int maxWidth = -1;
    
    /**
     * This is prepended to the second and subsequent output
     * lines. Used to generate labels that have left aligned lines but
     * with the first line containing a leading number that is
     * "outside" the alignment (hanging text).
     */
    protected String line2Prefix;
    
    /**
     * List of Strings. These lines do NOT take alignment into
     * account. That's done in print(), not in render().
     */
    protected ArrayList<String> output = new ArrayList<String>(2);
    
    /** Length of longest line in output array. */
    protected int width = 0;
    
    //protected StringEncoder stringEncoder = NullEncoder.instance;
    
    public LabelTextRenderer(Label label, int maxWidth) {
        this.label = label;
        this.maxWidth = maxWidth;
    }
    
    public void setLine2Indent(int n) {
        if (maxWidth >= 0 && n >= maxWidth) {
            throw new IllegalArgumentException();
        }
        StringBuilder sb = new StringBuilder(n);
        for (int i = 0; i < n; i++) {
            sb.append(' ');
        }
        line2Prefix = sb.toString();
    }
    
    /*
    public void setStringEncoder(StringEncoder stringEncoder) {
        this.stringEncoder = stringEncoder;
    }
    */
    
    public void render() {
        StringBuilder sb = new StringBuilder(80);
        LabelTokenizer tok = new LabelTokenizer(label.getText());
        while (tok.hasNext()) {
            final int len = sb.length();
            String s = tok.nextToken();
            if (maxWidth < 0 || len == 0 || len + s.length() < maxWidth) {
                // append to current line
                sb.append(s);
            } else {
                // add to a new line
                addToOutput(sb);
                sb.append(s);
            }
            if (tok.forcedLineBreak()) {
                addToOutput(sb);
            }
        }
        if (sb.length() > 0) {
            addToOutput(sb);
        }
    }
    
    private void addToOutput(StringBuilder sb) {
        if (width < sb.length()) width = sb.length();
        output.add(sb.toString());
        sb.setLength(0);
        if (line2Prefix != null) {
            sb.append(line2Prefix);
        }
    }
    
    public int getWidth() {
        return width;
    }
    
    public int getHeight() {
        return output.size();
    }
    
    /**
     * Renders 'output' on the specified canvas. Takes into account
     * xref and textAlignment.
     * 
     * @param canvas the canvas to print to
     * @param linenum line of canvas containing *first* line of output
     */
    public void print(TextCanvas canvas, int linenum) {
        print(canvas, linenum, null);
    }
    
    public void print(TextCanvas canvas, int linenum, StringEncoder stringEncoder) {
        Alignment a = label.getTextAlignment();
        int x = getX();
        if (logger.isDebugEnabled()) {
            logger.debug("print: " + linenum + "," + x +
                         ",align=" + a +
                         ", line0=[" + getLine0() + "]");
        }
        for (int i = 0; i < output.size(); i++) {
            String s = (String) output.get(i);
            if (s != null && stringEncoder != null) {
                s = stringEncoder.encode(s);
            }
            if (a == Alignment.LEFT) {
                canvas.print(linenum, x, s);
            } else if (a == Alignment.CENTER) {
                canvas.print(linenum, x + (width-s.length())/2, s);
            } else if (a == Alignment.RIGHT) {
                canvas.print(linenum, x+(width-s.length()), s);
            }
            linenum++;
        }
    }
    
    private String getLine0() {
        return (output.size() == 0 ? null : (String) output.get(0));
    }
    
    /**
     * Given Label's anchorPos, labelPos and width and HAnchor's start
     * column and width, figure out left-most x coordinate.
     */
    private int getX() {
        HAnchor anchor = label.getAnchor();
        
        if (logger.isDebugEnabled()) {
            logger.debug("getX: anchorPos=" + label.getAnchorPos() +
                         ",labelPos=" + label.getLabelPos() +
                         ",width=" + width);
        }
        
        int xref; // x-pos where label attached
        switch (label.getAnchorPos()) {
        case LEFT:
            xref = anchor.getStart();
            break;
        case CENTER:
            xref = anchor.getStart() + (anchor.getWidth()/2);
            break;
        case RIGHT:
            xref = anchor.getStart() + anchor.getWidth();
            break;
        default:
            throw new IllegalArgumentException();
        }
        
        switch (label.getLabelPos()) {
        case LEFT:
            // xref is correct
            break;
        case CENTER:
            xref -= (width/2);
            break;
        case RIGHT:
            xref -= width;
            break;
        default:
            throw new IllegalArgumentException();
        }
        
        if (logger.isDebugEnabled()) {
            logger.debug("getX: returning " + xref);
        }
        
        return xref;
    }
    
    
    ///////////////////////////////////////////////////////////////////////
    // unit testing (XXX: move to separate UT dir)
    
    public static void main(String[] args) {
        Label label = new Label(args[0]);
        LabelTextRenderer tl = new LabelTextRenderer(label, Integer.parseInt(args[1]));
        tl.render();
        for (int i = 0; i < tl.getHeight(); i++) {
            System.out.println("[" + tl.output.get(i) + "]");
        }
    }
}
